package loader

import (
	"os"
	"fmt"
	"sort"
	"errors"
	"strings"
	"path/filepath"
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/compiler/loader/manifest"
)


type ModuleThunk struct {
	ModPath   string
	FileInfo  os.FileInfo
	Manifest  manifest.Module
	Content   Content
}
type Content interface {
	Load(ctx context)  (*ast.Root, *Error)
}
type PredefinedAST struct {
	Root  *ast.Root
}
type StandaloneScript struct {
	File  SourceFileTextual
}
type ModuleFolder struct {
	Files  [] SourceFile
}
func (m PredefinedAST) Load(context) (*ast.Root, *Error) {
	return m.Root, nil
}
func (mod StandaloneScript) Load(ctx context) (*ast.Root, *Error) {
	var root, err = mod.File.GetAST()
	if err != nil { return nil, &Error {
		Context:  ctx,
		Concrete: err,
	} }
	return root, nil
}
func (mod ModuleFolder) Load(ctx context) (*ast.Root, *Error) {
	var ast_root = craftEmptyAST("(Module Folder)")
	for _, f := range mod.Files {
		var f_root, err = f.GetAST()
		if err != nil { return nil, &Error {
			Context:  ctx,
			Concrete: err,
		} }
		for _, cmd := range f_root.Statements {
			ast_root.Statements = append(ast_root.Statements, cmd)
		}
	}
	return &ast_root, nil
}

type FileInfoList ([] os.FileInfo)
func (l FileInfoList) Len() int {
	return len(l)
}
func (l FileInfoList) Less(i, j int) bool {
	return (l[i].Name() < l[j].Name())
}
func (l FileInfoList) Swap(i, j int) {
	var t = l[i]
	l[i] = l[j]
	l[j] = t
}

func readModulePath(path string, fs FileSystem, is_root bool) (ModuleThunk, error) {
	path = filepath.Clean(path)
	var f_info, f_content, f_entries, err = ReadFileOrFolder(path, fs)
	if err != nil { return ModuleThunk{}, err }
	if f_info.IsDir() {
		var items = f_entries
		sort.Sort(FileInfoList(items))
		var has_manifest = false
		var manifest_content ([] byte)
		var unit_files = make([] SourceFile, 0)
		for _, item := range items {
			var item_name = item.Name()
			var item_path = filepath.Join(path, item_name)
			if item_name == ManifestFileName {
				var item_content, err = ReadFile(item_path, fs)
				if err != nil { return ModuleThunk{}, err }
				manifest_content = item_content
				has_manifest = true
			}
		}
		if !(has_manifest) {
			return ModuleThunk{}, errors.New("missing module manifest")
		}
		mod, err := manifest.Load(manifest_content)
		if err != nil {
			return ModuleThunk{}, errors.New(fmt.Sprintf(
				"error decoding module manifest: %s", err.Error()))
		}
		for _, item := range items {
			var item_name = item.Name()
			var item_path = filepath.Join(path, item_name)
			if strings.HasSuffix(item_name, SourceSuffix) {
				var item_content, err = ReadFile(item_path, fs)
				if err != nil { return ModuleThunk{}, err }
				var ref = FileRef {
					FS:   fs,
					Path: item_path,
				}
				unit_files = append(unit_files, SourceFileTextual {
					FileRef: ref,
					Content: item_content,
				})
			} else {
				// do nothing
			}
		}
		return ModuleThunk {
			ModPath:  path,
			FileInfo: f_info,
			Manifest: mod,
			Content:  ModuleFolder { unit_files },
		}, nil
	} else {
		if !(is_root) {
			return ModuleThunk{},
				errors.New("dependency must be a folder")
		}
		var content = f_content
		var ref = FileRef {
			FS:   fs,
			Path: path,
		}
		return ModuleThunk {
			ModPath:  path,
			FileInfo: f_info,
			Manifest: manifest.Module {
				UniqueId: StandaloneScriptModuleId,
			},
			Content: StandaloneScript {
				File: SourceFileTextual {
					FileRef: ref,
					Content: content,
				},
			},
		}, nil
	}
}


